#include "slag_version.h"
#define USAGE "GoGo Build System " VERSION "\nUsage: gogo [target-name]\n"

/*
 *=============================================================================
 *  gogo.cpp
 *
 *  $(SLAG_VERSION)
 *  ---------------------------------------------------------------------------
 *
 *  Copyright 2008-2011 Plasmaworks LLC
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at 
 *
 *    http://www.apache.org/licenses/LICENSE-2.0 
 *
 *  Unless required by applicable law or agreed to in writing, 
 *  software distributed under the License is distributed on an 
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
 *  either express or implied. See the License for the specific 
 *  language governing permissions and limitations under the License.
 *
 *  ---------------------------------------------------------------------------
 *
 *  History:
 *    2009.10.16 / Abe Pralle - Created
 *=============================================================================
*/

#include "slag.h"
#include "unzip.h"
#include "zip.h"

#if defined(_WIN32)
#  define SLAGC_FILENAME  "slagc.exe"
#  define PATH_CHAR       "\\"
#else
#  define SLAGC_FILENAME  "slagc"
#  define PATH_CHAR       "/"
#endif

char** final_args  = 0;
char*  bin_path    = 0;
char*  src_path    = 0;
char*  slagc_path  = 0;

void ZipArchive__decompress__Int32();
void ZipArchive__load_entries();
void ZipArchive__load_raw__Int32();
void ZipArchive__add__String_Int64_ArrayList_of_Byte_Int32_Int32_Logical_Int32_Int32();

void error( const char* mesg );
int  process_args( int argc, char** argv );
bool exists( const char* filename );
void find_executables();
void check_build_etc();
void compile_buildfile();

int main( int argc, char** argv )
{
  try
  {
    slag_set_raw_exe_filepath( argv[0] );

    argc = process_args( argc-1, argv+1 );
    argv = final_args;

    find_executables();
    check_build_etc();

    SlagLoader loader;
    loader.load("build.etc");

    svm.hook_native( "ZipArchive", "decompress(Int32)",  ZipArchive__decompress__Int32 );
    svm.hook_native( "ZipArchive", "load_entries()",  ZipArchive__load_entries );
    svm.hook_native( "ZipArchive", "load_raw(Int32)", ZipArchive__load_raw__Int32 );
    svm.hook_native( "ZipArchive", 
        "add(String,Int64,ArrayList<<Byte>>,Int32,Int32,Logical,Int32,Int32)",
        ZipArchive__add__String_Int64_ArrayList_of_Byte_Int32_Int32_Logical_Int32_Int32 );

    slag_configure();
    slag_set_command_line_args( argv, argc );
    slag_launch();
    slag_shut_down();

    if (final_args)  delete final_args;
    if (slagc_path)  delete slagc_path;

    printf( "\n" );
    return 0;
  }
  catch (int err)
  {
    if (err && slag_error_message.value) fprintf( stderr, "%s\n", slag_error_message.value );
    return err;
  }
}

void error( const char* mesg )
{
  fprintf( stderr, "%s", mesg );
  fprintf( stderr, "\n" );
  //fprintf( stderr, "Visit http://plasmaworks.com/gogo for help.\n" );
  exit(1);
}

int process_args( int argc, char** argv )
{
  final_args = new char*[argc];
  memset( final_args, 0, sizeof(char*)*argc );
  int final_count = 0;
  for (int i=0; i<argc; ++i)
  {
    if (0==strcmp(argv[i],"-bin"))
    {
      if (i+1 == argc)
      {
        error( "Path expected after -bin" );
      }
      bin_path = argv[++i];
    }
    else if (0==strcmp(argv[i],"-src_path"))
    {
      if (i+1 == argc)
      {
        error( "Path expected after -src_path" );
      }
      src_path = argv[++i];
    }
    else if (0==strcmp(argv[i],"-create"))
    {
      ++i;
      FILE* fp = fopen( "build.slag", "rb" );
      if (fp)
      {
        printf( "-create: build.slag already exists!\n" );
      }
      else
      {
        fp = fopen( "build.slag", "w" );
        fprintf( fp, "class Main : GoGo\n" );
        fprintf( fp, "  METHODS\n" );
        fprintf( fp, "    method build( String cmd )\n" );
        fprintf( fp, "      which (cmd)\n" );
        fprintf( fp, "        case \"hi\":\n" );
        fprintf( fp, "          execute(\"echo hi\")\n" );
        fprintf( fp, "        others:\n" );
        fprintf( fp, "          println( \"default build (try \\\"gogo hi\\\")\" )\n" );
        fprintf( fp, "      endWhich\n" );
        fprintf( fp, "endClass\n" );

        fclose(fp);
        printf( "Created build.slag starter file.\n" );
        exit(1);
      }
    }
    else
    {
      final_args[final_count++] = argv[i];
    }
  }
  return final_count;
}

bool exists( const char* filename )
{
  FILE* fp = fopen(filename,"rb");
  if ( !fp ) return false;
  fclose(fp);
  return true;
}

char* new_string( const char* st )
{
  int len = strlen(st);
  char* buffer = new char[len+1];
  strcpy(buffer,st);
  return buffer;
}

void find_executables()
{
  if (bin_path)
  {
    char filepath[PATH_MAX];
    sprintf( filepath, "%s%s%s", bin_path, PATH_CHAR, SLAGC_FILENAME );
    if ( !exists(filepath) ) error( SLAGC_FILENAME " not found on executable path." );
    slagc_path = new_string(filepath);
  }
  else
  {
    slagc_path  = new_string(SLAGC_FILENAME);
  }
}

void check_build_etc()
{
  if (!exists("build_core.slag") && !exists("build.slag"))
  {
    error( "No build file found.\nType \"gogo -create\" to create a build.slag starter file." );
  }

  if (!exists("build.slag"))
  {
    FILE* fp = fopen("build.slag","wb");

    fprintf( fp, "[include \"build_core.slag\"]\n" );
    fprintf( fp, "\n" );
    fprintf( fp, "class CustomBuild : BuildCore\n" );
    fprintf( fp, "  METHODS\n" );
    fprintf( fp, "    method build( String cmd ):\n" );
    fprintf( fp, "      prior.build(cmd)\n" );
    fprintf( fp, "endClass\n" );
    fclose(fp);
  }

  compile_buildfile();
}

void compile_buildfile()
{
  char cmd[PATH_MAX];
  sprintf( cmd, "%s build.slag -include gogo.slag -quiet", slagc_path );
  if (bin_path && !src_path) 
  {
    src_path = new char[strlen(bin_path)*2 + 64];
    sprintf( src_path, 
      "%s" PATH_CHAR "libraries;%s" PATH_CHAR "libraries" PATH_CHAR "standard",
      bin_path, bin_path );
  }
  if (src_path)
  {
    sprintf( cmd + strlen(cmd), " -src_path \"%s\"", src_path );
  }
  //printf( "\n> %s\n", cmd );
  if (0 != system(cmd))
  {
    error( "BUILD FAILED\n" );
  }
  //printf( "\n" );
}

void slag_adjust_filename_for_os( char* filename, int len )
{
}

void ZipArchive__decompress__Int32()
{
  // ZipArchive::decompress(Int32).Byte[]
  SlagInt32 index = (SlagInt32) SLAG_POP_INTEGER();
  SlagObject* archive_obj = SLAG_POP_REF();
  SVM_NULL_CHECK( archive_obj, svm.type_null_reference_error );

  SLAG_GET_REF( archive_filename_obj, archive_obj, "archive_filename" );
  SVM_NULL_CHECK( archive_filename_obj, svm.type_null_reference_error );

  char archive_filename[PATH_MAX];
  ((SlagString*)archive_filename_obj)->to_ascii( archive_filename, PATH_MAX );

  unzFile zfp = unzOpen( archive_filename );

  if (zfp)
  {
    int status = unzGoToFirstFile(zfp);
    while (UNZ_OK == status)
    {
      if (index == 0)
      {
        unz_file_info file_info;
        status = unzGetCurrentFileInfo( zfp, &file_info, NULL, 0, NULL, 0, NULL, 0 );
        if (UNZ_OK != status) break;

        //int crc32 = file_info.crc;
        //int compressed_count = file_info.compressed_size;
        int uncompressed_count = file_info.uncompressed_size;

        SlagInt64 ztime = file_info.tmu_date.tm_year;  // year eg 2009
        ztime = (ztime * 100) + (file_info.tmu_date.tm_mon + 1);  // month 1..12
        ztime = (ztime * 100) + (file_info.tmu_date.tm_mday);  // day 1..31
        ztime = (ztime * 100) + (file_info.tmu_date.tm_hour);  // hour 0..23
        ztime = (ztime * 100) + (file_info.tmu_date.tm_min);   // minute 0..59
        ztime = (ztime * 100) + (file_info.tmu_date.tm_sec);   // second 0..59

        SLAG_FIND_TYPE( type_byte_array, "Array<<Byte>>" );
        SlagArray*    bytes = type_byte_array->create( uncompressed_count );
        SLAG_PUSH_REF( bytes );

        unzOpenCurrentFile( zfp );
        unzReadCurrentFile( zfp, ((char*) bytes->data), uncompressed_count );
        unzClose(zfp);
        return;
      }
      status = unzGoToNextFile(zfp);
      --index;
    }
  }

printf("error 1\n");
  slag_throw_file_error( archive_filename );
  return;
}


void ZipArchive__load_entries()
{
  SlagObject* archive_obj = SLAG_PEEK_REF();
  SVM_NULL_CHECK( archive_obj, svm.type_null_reference_error );

  SLAG_GET_REF( archive_filename_obj, archive_obj, "archive_filename" );
  SVM_NULL_CHECK( archive_filename_obj, svm.type_null_reference_error );

  char archive_filename[PATH_MAX];
  ((SlagString*)archive_filename_obj)->to_ascii( archive_filename, PATH_MAX );

  unzFile zfp = unzOpen( archive_filename );
  if ( !zfp ) 
  {
    zfp = zipOpen( archive_filename, APPEND_STATUS_CREATE );
    zipClose( zfp, NULL );
    SLAG_POP_REF();
    return;
  }

  int index = 0;
  int status = unzGoToFirstFile(zfp);
  while (UNZ_OK == status)
  {
    char current_filename[256+1];
    unz_file_info file_info;
    status = unzGetCurrentFileInfo( zfp, &file_info, current_filename, 256, NULL, 0, NULL, 0 );
    if (UNZ_OK == status)
    {
      SlagInt64 ztime = file_info.tmu_date.tm_year;  // year eg 2009
      ztime = (ztime * 100) + (file_info.tmu_date.tm_mon + 1);  // month 1..12
      ztime = (ztime * 100) + (file_info.tmu_date.tm_mday);  // day 1..31
      ztime = (ztime * 100) + (file_info.tmu_date.tm_hour);  // hour 0..23
      ztime = (ztime * 100) + (file_info.tmu_date.tm_min);   // minute 0..59
      ztime = (ztime * 100) + (file_info.tmu_date.tm_sec);   // second 0..59

      SLAG_PUSH_REF( archive_obj );
      SLAG_PUSH_INTEGER( index );
      SLAG_PUSH_REF( SlagString::create(current_filename) );
      SLAG_PUSH_INTEGER( ztime );  // timestamp
      SLAG_CALL( archive_obj->type, "add_entry(Int32,String,Int64)" );

      status = unzGoToNextFile(zfp);
    }
    ++index;
  }
  unzClose(zfp);

  SLAG_POP_REF();  // archive_obj
}

void ZipArchive__load_raw__Int32()
{
  // ZipArchive::load_raw(Int32).RawZipData
  SlagInt32 index = (SlagInt32) SLAG_POP_INTEGER();
  SlagObject* archive_obj = SLAG_POP_REF();
  SVM_NULL_CHECK( archive_obj, svm.type_null_reference_error );

  SLAG_GET_REF( archive_filename_obj, archive_obj, "archive_filename" );
  SVM_NULL_CHECK( archive_filename_obj, svm.type_null_reference_error );

  char archive_filename[PATH_MAX];
  ((SlagString*)archive_filename_obj)->to_ascii( archive_filename, PATH_MAX );

  unzFile zfp = unzOpen( archive_filename );

  int original_index = index;
  if (zfp)
  {
    int status = unzGoToFirstFile(zfp);
    while (UNZ_OK == status)
    {
      if (index == 0)
      {
        unz_file_info file_info;
        status = unzGetCurrentFileInfo( zfp, &file_info, NULL, 0, NULL, 0, NULL, 0 );
        if (UNZ_OK != status) 
        {
          printf( "[Internal] Failed to get current file info." ); 
          break;
        }

        int crc32 = file_info.crc;
        int compressed_count = file_info.compressed_size;
        int uncompressed_count = file_info.uncompressed_size;

        SlagInt64 ztime = file_info.tmu_date.tm_year;  // year eg 2009
        ztime = (ztime * 100) + (file_info.tmu_date.tm_mon + 1);  // month 1..12
        ztime = (ztime * 100) + (file_info.tmu_date.tm_mday);  // day 1..31
        ztime = (ztime * 100) + (file_info.tmu_date.tm_hour);  // hour 0..23
        ztime = (ztime * 100) + (file_info.tmu_date.tm_min);   // minute 0..59
        ztime = (ztime * 100) + (file_info.tmu_date.tm_sec);   // second 0..59

        int compression_method, compression_level;

        unzOpenCurrentFile2( zfp, &compression_method, &compression_level, 1 );

        SLAG_FIND_TYPE( type_raw_zip_data, "RawZipData" );
        SLAG_PUSH_REF( type_raw_zip_data->create() );
        SLAG_DUPLICATE_REF();

        SLAG_FIND_TYPE( type_byte_array, "Array<<Byte>>" );
        SlagArray* array = type_byte_array->create(compressed_count);
        SLAG_PUSH_REF( array );
        char* data = (char*) array->data;

        unzReadCurrentFile( zfp, data, compressed_count );

        SLAG_PUSH_INTEGER( uncompressed_count );
        SLAG_PUSH_INTEGER( ztime );
        SLAG_PUSH_INTEGER( compression_method );
        SLAG_PUSH_INTEGER( compression_level );
        SLAG_PUSH_INTEGER( crc32 );

        SLAG_CALL( type_raw_zip_data, "init(Array<<Byte>>,Int32,Int64,Int32,Int32,Int32)" )
        unzClose(zfp);
        return;
      }
      status = unzGoToNextFile(zfp);
      --index;
    }
    printf( "[Internal] Failed to advance to next file." ); 
  }
  else
  {
    printf( "[Internal] Failed to open archive." ); 
  }

  // This happens once in a while - gradually tracking down what's happening
  // with the printfs above.
  printf( "Please try again\n" );
  slag_throw_file_error( archive_filename );
  return;
}

void ZipArchive__add__String_Int64_ArrayList_of_Byte_Int32_Int32_Logical_Int32_Int32()
{
  SlagInt32 crc32 = (SlagInt32) SLAG_POP_INTEGER();
  SlagInt32 uncompressed_count = (SlagInt32) SLAG_POP_INTEGER();
  SlagInt32 raw = (SlagInt32) SLAG_POP_INTEGER();

  SlagInt32 compression_level = (SlagInt32) SLAG_POP_INTEGER();
  if (compression_level < 0) compression_level = 0;
  else if (compression_level > 9) compression_level = 9;

  SlagInt32 compression_method = (SlagInt32) SLAG_POP_INTEGER();
  if (compression_method != 0) compression_method = Z_DEFLATED;

  SlagArrayList* byte_list = (SlagArrayList*) SLAG_POP_REF();
  SlagInt64 ztime = SLAG_POP_INTEGER();
  SlagString* filename_obj = (SlagString*) SLAG_POP_REF();
  SlagObject* archive_obj = SLAG_PEEK_REF();

  SVM_NULL_CHECK( archive_obj, svm.type_null_reference_error );
  SVM_NULL_CHECK( filename_obj, svm.type_null_reference_error );
  SVM_NULL_CHECK( byte_list, svm.type_null_reference_error );

  SLAG_GET_REF( archive_filename_obj, archive_obj, "archive_filename" );
  SVM_NULL_CHECK( archive_filename_obj, svm.type_null_reference_error );
  char archive_filename[PATH_MAX];
  ((SlagString*)archive_filename_obj)->to_ascii( archive_filename, PATH_MAX );

  char filename[PATH_MAX];
  filename_obj->to_ascii( filename, PATH_MAX );

  tm_zip timestamp;
  timestamp.tm_year = (int) ((ztime/10000000000LL) % 10000);
  timestamp.tm_mon  = (int) ((ztime/100000000) % 100) - 1; 
  timestamp.tm_mday = (int) ((ztime/1000000) % 100); 
  timestamp.tm_hour = (int) ((ztime/10000) % 100); 
  timestamp.tm_min  = (int) ((ztime/100) % 100); 
  timestamp.tm_sec  = (int) (ztime % 100); 

  int count = byte_list->count;
  SlagByte* data = (SlagByte*) byte_list->array->data;

  zipFile zfp = zipOpen( archive_filename, APPEND_STATUS_ADDINZIP );
  if ( !zfp ) zfp = zipOpen( archive_filename, APPEND_STATUS_CREATE );

  if ( !zfp )
  {
printf("error 3\n");
    slag_throw_file_error( archive_filename );
    return;
  }

  zip_fileinfo file_info;
  memset( &file_info, 0, sizeof(zip_fileinfo) );
  file_info.tmz_date = timestamp;

  int error = zipOpenNewFileInZip2( zfp, filename, &file_info,
      NULL, 0, NULL, 0, NULL, 
      compression_method, compression_level, raw );

  if (0 == error) error |= zipWriteInFileInZip( zfp, data, count );

  if (raw) zipCloseFileInZipRaw( zfp, uncompressed_count, crc32 );
  else     zipCloseFileInZip(zfp);

  zipClose( zfp, NULL );

  if (error)
  {
printf("error 4\n");
    slag_throw_file_error( archive_filename );
    return;
  }

  SLAG_POP_REF();
}

